/****************************************************************************** * Copyright (C) Ultraleap, Inc. 2011-2021. * * * * Use subject to the terms of the Apache License 2.0 available at * * http://www.apache.org/licenses/LICENSE-2.0, or another agreement * * between Ultraleap and you, your company or other organization. * ******************************************************************************/ using System.Collections; using System.Collections.Generic; using UnityEngine; namespace Leap.Unity.Interaction { /// /// This pose handler is the default implementation of IGraspedPoseHandler provided by /// the Interaction Engine and is most likely the only implementation you will need. It /// uses a Kabsch solve from frame to frame based on the points at which any grasping /// controller are grasping the interaction object to determine where the object should /// move and rotate in the grasp. Note that IGraspedPoseHandlers only determine the /// target position and rotation of a held object; actually moving the object is the /// domain of an IGraspedMovementHandler. /// public class KabschGraspedPose : IGraspedPoseHandler { public const int NUM_FINGERS = 5; public const int NUM_BONES = 4; public enum SolveMethod { SixDegreeSolve, PivotAroundOrigin } private SolveMethod _solveMethod; private InteractionBehaviour _intObj; private KabschSolver _kabsch; private List _points, _refPoints; private Vector3 _controllerCentroid, _objectCentroid; private float _manipulatorCount; private Dictionary _controllerToPoints; public KabschGraspedPose(InteractionBehaviour interactionObj) { _intObj = interactionObj; _kabsch = new KabschSolver(); _points = new List(20); _refPoints = new List(20); _controllerToPoints = new Dictionary(); } public void AddController(InteractionController controller) { var newPoints = PosePointCollection.Create(_intObj.rigidbody.position, _intObj.rigidbody.rotation); _controllerToPoints[controller] = newPoints; for (int i = 0; i < controller.graspManipulatorPoints.Count; i++) { Vector3 manipulatorPosition = controller.graspManipulatorPoints[i]; newPoints.SetWorldPosition(i, manipulatorPosition); } } public void RemoveController(InteractionController controller) { var collection = _controllerToPoints[controller]; _controllerToPoints.Remove(controller); // Return the collection to the pool so it can be re-used. PosePointCollection.Return(collection); } public void ClearControllers() { foreach (var controllerPointsPair in _controllerToPoints) { PosePointCollection.Return(controllerPointsPair.Value); } _controllerToPoints.Clear(); } public void GetGraspedPosition(out Vector3 newPosition, out Quaternion newRotation) { _points.Clear(); _refPoints.Clear(); Vector3 bodyPosition = _intObj.rigidbody.position; Quaternion bodyRotation = _intObj.rigidbody.rotation; Matrix4x4 it = Matrix4x4.TRS(bodyPosition, bodyRotation, Vector3.one); _controllerCentroid = Vector3.zero; _objectCentroid = Vector3.zero; _manipulatorCount = 0f; foreach (var controllerPointPair in _controllerToPoints) { InteractionController controller = controllerPointPair.Key; PosePointCollection points = _controllerToPoints[controller]; for (int i = 0; i < controller.graspManipulatorPoints.Count; i++) { Vector3 originalManipulatorPos = points.GetLocalPosition(i); Vector3 currentManipulatorPos = controller.graspManipulatorPoints[i]; // Perform the solve such that the objects' positions are matched to the new // manipulator positions. Vector3 point1 = (it.MultiplyPoint3x4(originalManipulatorPos) - bodyPosition); Vector3 point2 = (currentManipulatorPos - bodyPosition); if (_intObj.isPositionLocked) { // Only rotate the object, pivoting around its origin. _solveMethod = SolveMethod.PivotAroundOrigin; _objectCentroid += point1; _controllerCentroid += point2; _manipulatorCount += 1F; } else { // Do normal Kabsch solve. _solveMethod = SolveMethod.SixDegreeSolve; _points.Add(point1); _refPoints.Add(point2); } } } Matrix4x4 kabschTransform = PerformSolve(bodyPosition); newPosition = bodyPosition + kabschTransform.GetVector3(); newRotation = kabschTransform.GetQuaternion() * bodyRotation; } private Matrix4x4 PerformSolve(Vector3 position) { switch (_solveMethod) { case SolveMethod.SixDegreeSolve: return _kabsch.SolveKabsch(_points, _refPoints); case SolveMethod.PivotAroundOrigin: _objectCentroid /= _manipulatorCount; _controllerCentroid /= _manipulatorCount; if (!_objectCentroid.Equals(_controllerCentroid)) { return Matrix4x4.TRS(Vector3.zero, Quaternion.FromToRotation(_objectCentroid, _controllerCentroid), Vector3.one); } else { return Matrix4x4.identity; } default: return _kabsch.SolveKabsch(_points, _refPoints); } } protected class PosePointCollection { // Without a pool, you might end up with 2 instances per object // With a pool, likely there will only ever be 2 instances! private static Stack _posePointCollectionPool = new Stack(); private List _localPositions; private Matrix4x4 _inverseTransformMatrix; public static PosePointCollection Create(Vector3 position, Quaternion rotation) { PosePointCollection collection; if (_posePointCollectionPool.Count != 0) { collection = _posePointCollectionPool.Pop(); } else { collection = new PosePointCollection(); } collection.Initialize(position, rotation); return collection; } public static void Return(PosePointCollection posePointCollection) { _posePointCollectionPool.Push(posePointCollection); } private PosePointCollection() { _localPositions = new List(); } private void Initialize(Vector3 position, Quaternion rotation) { _inverseTransformMatrix = Matrix4x4.TRS(position, rotation, Vector3.one).inverse; } public void SetWorldPosition(int index, Vector3 worldPosition) { Vector3 localPosition = _inverseTransformMatrix.MultiplyPoint3x4(worldPosition); if (index > _localPositions.Count) { Debug.LogError("SetWorldPosition requires setting indices in order from 0."); } if (_localPositions.Count == index) { _localPositions.Add(localPosition); } else { _localPositions[index] = localPosition; } } public Vector3 GetLocalPosition(int index) { return _localPositions[index]; } } } }